local base = _G
local ipairs = base.ipairs
local assert = base.assert
local table = base.table
module("gui")

-- Container object using layer

ContainerG = {}
ContainerGMT = { __index = ContainerG }

base.setmetatable(ContainerG, { __index = ObjectG })

ContainerG.create = function(x, y, width, height, mt)
  local self = Object(x, y, width, height, mt or ContainerGMT)

  -- make sure the sprite is below any child nodes
  self.sprite.depth = 1
  self.sprite:set_position(0, 0)
  self.layer = base.Layer(x, -y) -- flip y axis
  self.layer:add_child(self.sprite)
  self.node = self.layer

  self.objects = {}
  self.focus = nil
  self.hover = nil
  self.exclusive = false

  return self
end

ContainerG.destroy = function(self)
  self:destroy_children()
  
  self.layer:remove_child(self.sprite)
  self.layer = nil
  self.objects = nil
  self.focus = nil
  self.exclusive = nil
  self.hover = nil
  ObjectG.destroy(self)
end

ContainerG.get_child = function(self, child)
  for i, v in ipairs(self.objects) do
    if v == child then
      return i
    end
  end
end

ContainerG.add_child = function(self, child)
  assert(child, "Child node is nil")
  -- unreference child from previous parent
  local parent = child:get_parent()
  if parent then
    parent:remove_child(child)
  end
  -- todo: container shouldn't know about viewports
  local mt = base.getmetatable(child.node)
  if mt == base.Sprite or mt == base.Layer then
    self.layer:add_child(child.node)
  elseif mt == base.Viewport then
    assert(self.viewport, "Child node type unsupported by this container")
    self.viewport:add_child(child.node)
  end
  table.insert(self.objects, child)
  -- reference child from new parent
  child:set_parent(self)
end

ContainerG.remove_child = function(self, child)
  assert(child, "Child node is nil")
  if self.focus == child then
    self.focus = nil
    self.exclusive = false
  end
  if self.hover == child then
    self.hover = nil
  end
  local i = self:get_child(child)
  assert(i, "Child node is not parented by this container")
  -- todo: container shouldn't know about viewports
  local mt = base.getmetatable(child.node)
  if mt == base.Sprite or mt == base.Layer then
    self.layer:remove_child(child.node)
  elseif mt == base.Viewport then
    self.viewport:remove_child(child.node)
  end
  table.remove(self.objects, i)
  -- reference from child to parent
  child:set_parent(nil)
end

ContainerG.destroy_children = function(self)
  self.focus = nil
  self.exclusive = nil
  self.hover = nil

  while #self.objects > 0 do
    local v = table.remove(self.objects)
    -- todo: container shouldn't know about viewports
    local mt = base.getmetatable(v.node)
    if mt == base.Sprite or mt == base.Layer then
      self.layer:remove_child(v.node)
    else
      self.viewport:remove_child(v.node)
    end
    -- reference from child to parent
    v:set_parent(nil)
    v:destroy()
  end
end

-- test child nodes for intersection
-- returns the top-most child according to depth-ordering
ContainerG.query_local = function(self, x, y)
  -- todo: avoid creating new tables
  local top, depth
  for i, v in ipairs(self.objects) do
    local vd = v.node.depth
    if depth == nil or vd < depth then
      -- convert to local coords
      local lx, ly = v:to_local(x, y)
      if v:test_point(lx, ly) then
        top = v
        depth = vd
      end
    end
  end
  return top
end

-- set focus to a given child object
ContainerG.set_focus = function(self, q, e)
  if self.focus and self.focus ~= q then
    self.focus.is_focused = false
  end
  if q then
    local i = self:get_child(q)
    assert(i, "Child node is not parented by this container")
  end
  self.focus = q
  self.exclusive = (e == true)
  if self.focus then
    self.focus.is_focused = true
  end
end

-- set hover to a given child object
ContainerG.set_hover = function(self, q)
  if self.hover and self.hover ~= q then
    self.hover.is_hovered = false
  end
  self.hover = q
  if self.hover then
    self.hover.is_hovered = true
  end
end

ContainerG.write = function(self, c)
  if self.focus then
    self.focus:write(c)
  end
end

ContainerG.key_command = function(self, key, ctrl, shift)
  if self.focus then
    self.focus:key_command(key, ctrl, shift)
  end
end

ContainerG.key_press = function(self, key)
  if self.focus then
    self.focus:key_press(key)
  end
end

ContainerG.key_release = function(self, key)
  if self.focus then
    self.focus:key_release(key)
  end
end

ContainerG.mouse_press = function(self, button, x, y)
  if self.exclusive == false then
    local q = self:query_local(x, y)
    self:set_focus(q)
    self:set_hover(q)
  end
  self:mouse_event('mouse_press', button, x, y)
end

ContainerG.mouse_release = function(self, ...)
  self:mouse_event('mouse_release', ...)
end

ContainerG.begin_drag = function(self, ...)
  self:mouse_event('begin_drag', ...)
end

ContainerG.end_drag = function(self, ...)
  self:mouse_event('end_drag', ...)
end

ContainerG.cancel_drag = function(self, ...)
  self:mouse_event('cancel_drag', ...)
end

ContainerG.dragging = function(self, ...)
  self:mouse_event('dragging', ...)
end

ContainerG.single_click = function(self, ...)
  self:mouse_event('single_click', ...)
end

ContainerG.double_click = function(self, ...)
  self:mouse_event('double_click', ...)
end

ContainerG.mouse_event = function(self, event, button, x, y)
  -- pass down the event to the object in focus
  local focus = self.focus
  if focus then
    local lx, ly = focus:to_local(x, y)
    focus[event](focus, button, lx, ly)
  end
end

ContainerG.mouse_move = function(self, x, y, x2, y2)
  local focus = self.focus
  if focus then
    local lx, ly = focus:to_local(x, y)
    local l2x, l2y = focus:to_local(x2, y2)
    focus:mouse_move(lx, ly, l2x, l2y)
  end
  local q = self:query_local(x, y)
  if self.exclusive == true and q ~= focus then
    return
  end
  self:set_hover(q)
--[[
  local hover = self.hover
  if hover then
    --todo
    local lx, ly = hover:to_local(x, y)
    local l2x, l2y = hover:to_local(x2, y2)
    hover:mouse_move(lx, ly, l2x, l2y)
  end
]]
end

ContainerG.wheel_move = function(self, z)
  if self.focus then
    self.focus:wheel_move(z)
  end
end

ContainerG.update = function(self, dt)
  if self.is_focused == false and self.focus then
    if self.exclusive == false then
      self:set_focus(nil)
    end
  end
  if self.is_hovered == false and self.hover then
    self:set_hover(nil)
  end
  for i, v in ipairs(self.objects) do
    v:update(dt)
  end
  -- todo: don't redraw all the time
  --self:redraw(dt)
end

Container = ContainerG.create